/**
 * @file spell.c
 * @brief spell functions.
 * @date Dec-2011
 * @author Leonardo Lino 2101101
 * @author Joaquim Ferreira 2101104
 * @version 1.0
 */
 
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "debug.h"
#include "files.h"
#include "memory.h"
#include "listas.h"
#include "hashtables.h"
#include "str_utils.h"
#include "spell.h"

/** @brief SPELL_OPTION 3 - A constant to use for define a type. */
#define SPELL_OPTION 3
/** @brief TOPERR_OPTION 4 - A constant to use for define a type. */
#define TOPERR_OPTION 4



/**< @brief Main processor for spell funcion.*/
void mainSpell(const char *dictionary, const char *filename){
	HASHTABLE_T* dictHolder;
	LISTA_GENERICA_T* fileHolder;
	ITERADOR_T* it;
	char *word=NULL;
	HASHTABLE_T* totalResults;
	LISTA_GENERICA_T* resultsList;
	int found=0;
	
	#ifdef SHOW_DEBUG
    DEBUG ("my debug \n\n");
    #endif
	
	/**< @brief Load dictionary.*/
	dictHolder= dictLoader(dictionary);

	/**< @brief Load textfile.*/
	//fileHolder= fileLoader(filename); //deprecated code. Maintained for debugging
	fileHolder= getLinesOrWords((char*)filename, NULL, SPELL_OPTION);
	
	
	/**< @brief Used to hold final results.*/
	totalResults = tabela_criar(101, (LIBERTAR_FUNC)free_tabela);
	
	/**< @brief Cycle through all words contained in textfile then compare them to dictionary,
	 * if word doesn't exist then search for anagrams and place their sinonims in resultsList */
	it = lista_criar_iterador(fileHolder);
	printf("Processing... \n");
	while ((word = (char*)iterador_proximo_elemento(it)) != NULL){
		resultsList = lista_criar(free);
		
		found = checkDict(dictHolder, resultsList, word, totalResults);
		if (found==0){
			tabela_inserir(totalResults, strdup(word), resultsList);
		}
			

	}
		
	iterador_destruir(&it);
	
	/**< @brief Output final results */
	outputResults(totalResults);
	
	
	/**< @brief Final process, where all memory should be released */
	lista_destruir(&fileHolder);
	assert(fileHolder == NULL);
	

	tabela_destruir(&totalResults);
	
	freeDictHolder(dictHolder);
	tabela_destruir(&dictHolder);
	assert(dictHolder == NULL);
	
}


void outputResults(HASHTABLE_T* totalResults){
	ITERADOR_T* it;
	ITERADOR_T* it2;
	char *str;
	LISTA_GENERICA_T* lista_aux;
	LISTA_GENERICA_T* lista;
	char *sin;
	int count=0;
	
	
	
	lista_aux = tabela_criar_lista_chaves(totalResults);
	it = lista_criar_iterador(lista_aux);
	
	/**< @brief Cycle through all elements of hashtable*/
	while ((str = (char*)iterador_proximo_elemento(it)) != NULL){
		count=0;
		lista=(LISTA_GENERICA_T*) tabela_consultar(totalResults,str);
		it2 = lista_criar_iterador(lista);

		if (str==NULL) continue;
		printf("%s:", str);
		/**< @brief Each element in hashtable holds a generic list, so we have to cycle through that list also */
		while ((sin = (char*)iterador_proximo_elemento(it2)) != NULL){
			if (count !=0) printf(",");
			printf("%s",sin);
			count++;
		}
		if (count<1) printf("?");
		printf("\n");
		iterador_destruir(&it2);
		lista_destruir(&lista);
	}
	
	iterador_destruir(&it);
	lista_destruir(&lista_aux);	
}

int compareSinonims(char *prev, char *new){
	return strcmp(prev, new);
}

/**< @brief Used for debugging purposes */
void outputList (LISTA_GENERICA_T* lista){
	ITERADOR_T* it;
	char *item;
	int i=0;
	
	it = lista_criar_iterador(lista);
	printf("-----List of Elements: --------\n");
	while ((item = (char*)iterador_proximo_elemento(it)) != NULL){
		printf("%d:%s  | ",i, item);
		i++;
	}
	printf("\n");
	iterador_destruir(&it);
	
}


/**< @brief Compare word against complete dictionary */
int checkDict(HASHTABLE_T* dictHolder, LISTA_GENERICA_T* resultsList, char *word, HASHTABLE_T* resultsHash){
	LISTA_GENERICA_T* lista_aux;
	ITERADOR_T* it;
	ITERADOR_T* it2;
	char *str= NULL;
	char *strSort = NULL;
	char *wordSort =NULL;
	LISTA_GENERICA_T* sinonims;
	char *sin = NULL;
	int ok = 0;
	
	
	/**< @brief Check if word is already in our final results hash. If yes then no need to process again  */
	str = tabela_consultar(resultsHash, word);
	if (str != NULL) return 1;
	
	/**< @brief Check if word is in dictionary. If yes then skip processing  */
	//str = tabela_consultar(dictHolder, word);
	//if (str != NULL) return 1;
	
	wordSort = strdup(word);
	/**< @brief sort word alphabetically. Method used to compare against dictionary keys  */
	reOrderSpell(wordSort);
	
	lista_aux = tabela_criar_lista_chaves(dictHolder);
	it = lista_criar_iterador(lista_aux);
	while ((str = (char*)iterador_proximo_elemento(it)) != NULL){
		if (strlen(str)<1) continue;
		
		if (strcmp(str,word)==0) {
			ok=1;
			break;
		}
		
		strSort= strdup(str);
		reOrderSpell(strSort);
		/**< @brief Check if reordered word and reordered dictionary key are a match */
		if (strcmp(strSort,wordSort)==0) {
			sinonims=(LISTA_GENERICA_T*) tabela_consultar(dictHolder,str);
			it2 = lista_criar_iterador(sinonims);
			
			/**< @brief Cycle through all sinonims and place them in resultsList */
			while ((sin = (char*)iterador_proximo_elemento(it2)) != NULL){
					lista_inserir_ordenado(resultsList,strdup(sin),(COMPARAR_FUNC)compareSinonims);
			}
			
			iterador_destruir(&it2);
		} 
		
		MY_FREE(strSort);

	}
	MY_FREE(wordSort);
	iterador_destruir(&it);
	lista_destruir(&lista_aux);
	if (ok==1) return 1;
	return 0;
}


/**< @brief Sort a string alphabetically */
void reOrderSpell(char *str){
	int i, j;
	char aux;
	int len;

	len = strlen(str);
	for(i=0; i < len-1; i++){
		for (j= i+1; j < len; j++){
			if (str[i]>str[j]){
				aux=str[j];
				str[j]=str[i];
				str[i]=aux;
			}
		}
	}
	
}


/**< @brief Deprecated function. Replaced by getLinesOrWords(). Maintained for debugging purposes */
LISTA_GENERICA_T *fileLoader(const char *filename){
	LISTA_GENERICA_T* lista;
	size_t len;
	ssize_t read;
	char *token = NULL;
	char *saveptr1 = NULL;
	char *line = NULL;
	char *subline = NULL;
	char *subtoken = NULL;
	char *saveptr2 = NULL;
	int linelen=0;
	
	
	lista = lista_criar(free);
	
	FILE *file_ptr = MY_FOPEN (filename, "r");
	

	while ((read = getline (&line, &len, file_ptr)) != -1) {
	for (token = strtok_r (line, ".!?", &saveptr1);
	   token != NULL; token = strtok_r (NULL, ".!?", &saveptr1)) {

			linelen = strlen (line);
			subline = (char *) MY_MALLOC (sizeof (char) * (linelen + 1));
			strcpy (subline, line);
			subline = strlower (subline);
			for (subtoken = strtok_r (subline, " _-:\",", &saveptr2);
				subtoken != NULL; subtoken = strtok_r (NULL, " _-:\",", &saveptr2)) {
				subtoken=trimWord (subtoken);
				subtoken = strlower (subtoken);
				if ((strlen(subtoken))>1){
					lista_inserir(lista, strdup(subtoken));
				}
				G_proc_lines++;
			}
	
		MY_FREE(subline);

	   }
	}
	MY_FREE(line);
	fclose (file_ptr);
	return lista;
}


/**< @brief Load dictionary into a hashtable, where each key has an associated generic list containing it's sinonims */
HASHTABLE_T  *dictLoader(const char *dictionary){
		HASHTABLE_T* tabela;
		ssize_t read;
		size_t len;
		char *token = NULL;
		char *line = NULL;
		char *line2 = NULL;
		char *saveptr1=NULL;
		LISTA_GENERICA_T* lista;
		char *chave = NULL;
		int i=0;
		
		
		tabela = tabela_criar(101, (LIBERTAR_FUNC)free_tabela);
		FILE *dic_ptr = MY_FOPEN (dictionary, "r");
		
		while ((read = getline (&line, &len, dic_ptr)) != -1) {
			
			line2 = strdup(line);
			
			i=1;
			lista = lista_criar(free);
			
			for (token = strtok_r (line2, ":,", &saveptr1);
			token != NULL; token = strtok_r (NULL, ":,", &saveptr1)) {
				token = trimWord(token);
				token = strlower (token);
				/**< @brief Since we're splitting the line either by ':' or ',' we need to check if this is the first item to use it as our hashtable key. */
				if (i==1){
					chave = strdup(token);
				/**< @brief Following check is performed to prevent empty elements in our list */
					if (strlen(token)>1)
						lista_inserir(lista, strdup(token));
				} else {
					/**< @brief Following check is performed to prevent empty elements in our list */
					if (strlen(token)>1)
						lista_inserir(lista, strdup(token));
				}
				G_proc_lines++;
				i++;
			}
			/**< @brief Now that we have our key and associated generic list with it's sinonims, we can insert it into our hashtable. */
			tabela_inserir(tabela, chave, lista);
			MY_FREE(chave);
			MY_FREE(line2);
		}
		
		
		
		MY_FREE(line);
		
		fclose (dic_ptr);
		return tabela;
	}

/**< @brief Routine used to free memory used by dictionary hashtable */
void freeDictHolder(HASHTABLE_T* dictHolder){
	LISTA_GENERICA_T* lista_aux;
	LISTA_GENERICA_T* lista;
	ITERADOR_T* it;
	
	lista_aux = tabela_criar_lista_elementos(dictHolder);
	it = lista_criar_iterador(lista_aux);
	/**< @brief Free associated lists */
	while ((lista = (LISTA_GENERICA_T*)iterador_proximo_elemento(it)) != NULL){
			lista_destruir(&lista);
	}
	iterador_destruir(&it);
	lista_destruir(&lista_aux);
}

/**< @brief Deprecated function maintained for backward compatibility */
void free_tabela(LISTA_GENERICA_T* lista){
	if(lista != NULL){}
}

